using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

namespace TaskFramework
{
    public class TaskPool : ITaskPool
    {
        private const int DEFAULT_MAX_CONCURRENT_TASK_COUNT = 4;
        private const string TIME_FORMAT = "{0:HH:mm:ss.fff}";

        private readonly ITaskQueue _waitQueue;
        private readonly Dictionary<Task, TaskInfo> _taskCache = new Dictionary<Task, TaskInfo>();
        private readonly Dictionary<TaskInfo, Thread> _activeTaskThreads = new Dictionary<TaskInfo, Thread>();
        private readonly ITaskTracker _taskTracker = new TaskTracker();
        private int _activeTaskCount;
        private readonly Thread _waitQueueConsumerThread;
        private readonly int _maxConcurrentTaskCount;
        private bool _writeDebugInfo;

        public TaskPool()
            : this(DEFAULT_MAX_CONCURRENT_TASK_COUNT)
        {
        }

        public TaskPool(int maxConcurrentTaskCount)
        {
            _maxConcurrentTaskCount = maxConcurrentTaskCount;

            _waitQueue = new TaskQueue(_taskTracker);
            _waitQueueConsumerThread = new Thread(WaitQueueConsumer);
            _waitQueueConsumerThread.Start();
        }

        public CreateTaskHandler CreateTask
        {
            set { _createTask = value; }
        }

        public event RootTaskDeadHandler RootTaskCompleted;

        private CreateTaskHandler _createTask;

        public void KillAll()
        {
            lock (_waitQueue)
            {
                _waitQueue.Clear();

                foreach (Thread taskThread in _activeTaskThreads.Values)
                {
                    try
                    {
                        taskThread.Interrupt();
                        taskThread.Join();
                    }
                    catch (Exception e)
                    {
                        Debug.WriteLine("TaskPool [KillAll]: \r\n" + e);
                    }
                }
            }
        }

        public void Kill(Task task)
        {
            throw new System.NotImplementedException();
        }

        public bool WriteDebugInfo
        {
            get { return _writeDebugInfo; }
            set { _writeDebugInfo = value; }
        }

        public int ActiveTaskCount
        {
            get { return _activeTaskCount; }
        }

        public void QueueTask(Task task, TaskEventHandler callback)
        {
            QueueTask(new TaskInfo(task, callback));
        }

        private void QueueTask(TaskInfo info)
        {
            if (_writeDebugInfo)
            {
                if (info.IsRootTask)
                {
                    Debug.WriteLine(string.Format((TIME_FORMAT + " TaskPool: {1} queued. [{2}]"), DateTime.Now, info.Task.GetType(), info.Task));
                }
                else
                {
                    Debug.WriteLine(string.Format((TIME_FORMAT + " TaskPool: {1} routed by task with id {2}. [{3}]"), DateTime.Now, info.Task.GetType(),info.RoutedByTaskId, info.Task));
                }
            }

            lock (_waitQueue)
            {
                AddTaskToWaitQueue(info);

                Monitor.Pulse(_waitQueue);
            }
        }

        /// <summary>
        /// Adds a task to the wait queue.
        /// To ensure thread safety, the _waitQueue object should be locked while calling this method.
        /// </summary>
        /// <param name="info"></param>
        private void AddTaskToWaitQueue(TaskInfo info)
        {
            if (info.TransferToPool)
            {
                info.TransferToPool= false;
            }
            else
            {
                _taskTracker.AddTask(info.Task.TaskId, info.RoutedByTaskId);
            }

            _waitQueue.Enqueue(info);
        }

        private void WaitQueueConsumer()
        {
            lock (_waitQueue)
            {
                while (_waitQueueConsumerThread.IsAlive)
                {
                    Monitor.Wait(_waitQueue);

                    while (_waitQueue.Count > 0 && _activeTaskCount < _maxConcurrentTaskCount)
                    {
                        TaskInfo info = _waitQueue.Dequeue();

                        if (!_taskCache.ContainsKey(info.Task))
                        {
                            _taskCache.Add(info.Task, info);
                        }
                        
                        if (_writeDebugInfo)
                        {
                            Debug.WriteLine(string.Format((TIME_FORMAT + " TaskPool: Starting thread for {1} [{2}]"), DateTime.Now, info.Task.GetType(), info.Task.RequestData));
                        }

                        Thread taskThread = new Thread(ExecuteTaskThread);
                        taskThread.Start(info);

                        _activeTaskCount++;
                        _activeTaskThreads.Add(info, taskThread);
                    }
                }
            }
        }

        private void ExecuteTaskThread(object taskObj)
        {
            TaskInfo info = (TaskInfo) taskObj;
            RoutingHelper router = new RoutingHelper(RouteRequest, info.TaskEventCallback, info);

            try
            {
                info.Task.Execute(router);

                if (!info.TransferToPool)
                {
                    FireTaskEvent(new TaskEvent(info), info.TaskEventCallback);
                }
            }
            catch (ThreadInterruptedException)
            {
                return;
            }
            catch (Exception e)
            {
                if (_writeDebugInfo)
                {
                    Debug.WriteLine(string.Format((TIME_FORMAT + " TaskPool: Exception thrown by {1} [{2}]\r\n{3}"), DateTime.Now, info.Task.GetType(), info.Task, e));
                }
                
                FireTaskEvent(new TaskEvent(info, e), info.TaskEventCallback);

                return;
            }
            finally
            {
                if (_writeDebugInfo)
                {
                    Debug.WriteLine(string.Format((TIME_FORMAT + " TaskPool: {1} completed execution. [{2}]"), DateTime.Now, info.Task.GetType(), info.Task.RequestData));
                }
                
                lock (_waitQueue)
                {
                    _activeTaskCount--;
                    _activeTaskThreads.Remove(info);

                    if (info.TransferToPool)
                    {
                        AddTaskToWaitQueue(info);
                    }
                    else
                    {
                        _taskTracker.RemoveTask(info.Task.TaskId);
                        _taskCache.Remove(info.Task);
                    }
                    
                    // Wake up WaitQueueConsumer
                    Monitor.Pulse(_waitQueue);
                }
            }
        }

        private void RouteRequest(Task routingTask, object requestData)
        {
            if (_createTask == null)
            {
                if (_writeDebugInfo)
                {
                    Debug.WriteLine(string.Format((TIME_FORMAT + " TaskPool: Cannot route request from task {1}. Please set the CreateTask delegate for the task pool. [{2}]"), DateTime.Now, routingTask.GetType(), requestData));
                }
            }
            else
            {
                Task task = _createTask(requestData);
                TaskInfo info = _taskCache[routingTask];

                QueueTask(new TaskInfo(task, info.TaskEventCallback, routingTask.TaskId));
            }
        }

        private static void FireTaskEvent(TaskEvent e, TaskEventHandler callback)
        {
            if (callback != null)
            {
                try
                {
                    callback.Invoke(e);
                }
                catch (Exception) { }
            }
        }
    }
}